﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;
using LLServer;
using LLServer.Chat;
using LLServer.Data;
using LLServer.Battle;

namespace LLWebService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class ClientService : IPortal
    {
        /// <summary>
        /// List of clients and its callback
        /// </summary>
        static Dictionary<Client, IPortalCallback> _onlines = new Dictionary<Client, IPortalCallback>();

        /// <summary>
        /// List of current battle
        /// </summary>
        static List<Battle> _currentBattle = new List<Battle>();

        /// <summary>
        /// Current object property
        /// </summary>
        Client _currentClient = null;

        /// <summary>
        /// Database connection variable
        /// </summary>
        Data dataConnection = new Data();

        #region Constant Variables
        private const int PATROL = 1;
        private const int SUBMARINE = 2;
        private const int DESTROYER = 3;
        private const int BATTLESHIP = 4;
        private const int CARRIER = 5;
        #endregion

        /// <summary>
        /// Constructor of ClientService class
        /// </summary>
        public ClientService()
        {
            // Subscribe event channel fault
            OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);

            // initialize Database Connection
            dataConnection.initialize();
        }

        /// <summary>
        /// Event if channel exception occurs
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Channel_Faulted(object sender, EventArgs e)
        {
            // if fault occurs, disconect client
            this.Disconnect();
        }

        /// <summary>
        /// Callback property for current project
        /// </summary>
        public IPortalCallback CurrentCallback
        {
            get { return OperationContext.Current.GetCallbackChannel<IPortalCallback>(); }
        }

        /// <summary>
        /// Get current battle by the name of captain
        /// </summary>
        private Battle _GetBattle(string captainName)
        {
            if (_currentBattle.Count > 0)
            {
                foreach (Battle b in _currentBattle)
                {
                    if (b.Cpt1.Name == captainName || b.Cpt2.Name == captainName)
                        return b;
                }
            }

            return null;
        }

        /// <summary>
        /// Get current battle by the name of client
        /// </summary>
        private Battle _GetBattleByUsername(string username)
        {
            if (_currentBattle.Count > 0)
            {
                foreach (Battle b in _currentBattle)
                {
                    if (b.Username1 == username || b.Username2 == username)
                        return b;
                }
            }

            return null;
        }

        /// <summary>
        /// Function to check wheter client is already in server or not
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        private bool _ValidateUser(string username)
        {
            // If client's username is already in list, return "false"
            if (this._GetUserByName(username) != null)
            {
                return false;
            }

            // If there is no client's username in list, return "true"
            return true;
        }

        /// <summary>
        /// Function to get certain callback
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        private IPortalCallback _GetUserCallback(string username)
        {
            // If client list is empty
            if (_onlines.Count != 0)
            {
                foreach (var client in _onlines.Keys)
                {
                    // If the client has been found, return its callback 
                    if (client.Username == username)
                    {
                        return _onlines[client];
                    }
                }
            }

            // If the client is not in the list, return null
            return null;
        }

        /// <summary>
        /// Function to get client by username
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        private Client _GetUserByName(string username)
        {
            if (_onlines.Count != 0)
            {
                foreach (var client in _onlines.Keys)
                {
                    if (string.Compare(client.Username, username, true) == 0)
                    {
                        return client;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Method to enter the session
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        public bool Connect(Client client)
        {
            bool userAdded = false;
            
            lock (_onlines)
            {
                // If client is valid (not in the online list)
                if (this._ValidateUser(client.Username) && dataConnection.Login(client))
                {
                    Captain cpt = dataConnection.GetCaptainByUsername(client.Username);
                    client.CaptainId = cpt.Id;
                    client.Username = client.Username;
                    client.CaptainName = cpt.Name;
                    client.Fraction = cpt.Fraction_id;
                    
                    // Set as current client
                    _currentClient = client;

                    // Add client to the list
                    _onlines.Add(client, this.CurrentCallback);
                    
                    // set bool as true
                    userAdded = true;
                }
            }

            // If adding process has been made
            if (userAdded)
            {
                // Do iteration on online clients
                foreach (Client cln in _onlines.Keys)
                {
                    // Contain the callback
                    IPortalCallback callback = _onlines[cln];
                    List<Client> list = _onlines.Keys.ToList<Client>();

                    try
                    {
                        // Refresh client list
                        callback.RefreshClient(list);

                        // Send call back to another client
                        callback.ReceiveNotifyClientConnect(client);
                    }
                    catch
                    {
                        // If there is exception, remove client
                        lock (_onlines)
                        {
                            _onlines.Remove(_currentClient);
                            _currentClient = null;
                        }

                        return false;
                    }
                }

                _RefreshAvailableUsers();
            }

            return userAdded;
        }

        /// <summary>
        /// Method to go out from session
        /// </summary>
        public void Disconnect()
        {
            if (_currentClient == null)
            {
                return;
            }

            // remove battle
            lock (_currentBattle)
            {
                Battle temp = _GetBattleByUsername(_currentClient.Username);
                if(temp!=null)
                    _currentBattle.Remove(temp);
            }

            lock (_onlines)
            {
                // Remove client from online list
                _onlines.Remove(_currentClient);
            }

            // Contain online list
            List<Client> list = _onlines.Keys.ToList<Client>();

            // loop on online list client
            foreach (IPortalCallback callback in _onlines.Values)
            {
                try
                {
                    // Send notify
                    callback.ReceiveNotifyClientDisconnect(_currentClient);
                }
                catch { }
            }

            // Set current client with null
            _currentClient = null;
        }

        /// <summary>
        /// Method to get online clients list
        /// </summary>
        public void Refresh()
        {
            this.CurrentCallback.RefreshClient(_onlines.Keys.ToList<Client>());
        }

        /// <summary>
        /// Method to send message to all online clients
        /// </summary>
        public void SendMessage(string content)
        {
            // Create message
            Message message = new Message { Sender = this._currentClient, Content = content, Time = DateTime.Now };

            foreach (IPortalCallback callback in _onlines.Values)
            {
                try
                {
                    // Send message to all clients
                    callback.ReceiveMessage(message);
                }
                catch { }
            }
        }


        /// <summary>
        /// Method to send private message
        /// </summary>
        /// <param name="target"></param>
        /// <param name="content"></param>
        public void SendWhisper(string target, string content)
        {
            // Create message
            Message message = new Message { Sender = this._currentClient, Content = content, Time = DateTime.Now };

            try
            {
                // Send message to target client
                this._GetUserCallback(target).ReceiveWhisper(message);
            }
            catch { }
        }

        /// <summary>
        /// Method to add captain (for testing only)
        /// </summary>
        public int AddCaptain(string captain_name)
        {
            int result = -1;

            result = dataConnection.AddCaptain(captain_name);

            return result;
        }


        public int SignUp(User user, Captain captain)
        {
            int result = -1;
            result = dataConnection.SignUp(user, captain);
            return result;
        }


        public void Challange(string target)
        {
            try
            {
                // Send challange to target client
                this._GetUserCallback(target).NotifyReceiveChallange(this._currentClient);
                
                // Notify that challange has been sent
                this._GetUserCallback(_currentClient.Username).NotifySendChallange(true);
                this._currentClient.IsAvailable = false;

                // refresh available users
                this._RefreshAvailableUsers();
            }
            catch {
                this._GetUserCallback(_currentClient.Username).NotifySendChallange(false);
                this._currentClient.IsAvailable = true;
            }
        }

        public void RespondChallange(string target, bool respond)
        {
            try
            {
                // send respond
                this._GetUserCallback(target).NotifyChallangeRespond(this._currentClient, respond);

                // if respond is true, then create a new battle
                if (respond)
                {
                    this._currentClient.IsAvailable = false;
                    _RefreshAvailableUsers();

                    // create a new battle
                    Battle battleNew = new Battle();
                    battleNew.Cpt1 = dataConnection.GetCaptainByUsername(target);
                    battleNew.Cpt2 = dataConnection.GetCaptainByUsername(_currentClient.Username);
                    battleNew.Username1 = target;
                    battleNew.Username2 = _currentClient.Username;

                    // add battle to list
                    _currentBattle.Add(battleNew);

                    // notify both players
                    Message m = new Message();
                    m.Sender = _GetUserByName(target);
                    m.Content = "New Battle";
                    this._GetUserCallback(target).NotifyBattleCreation(m);
                    this._GetUserCallback(_currentClient.Username).NotifyBattleCreation(m);
                }
                else
                {
                    this._GetUserByName(target).IsAvailable = true;
                    _RefreshAvailableUsers();
                }
            }
            catch
            {
            }
        }

        private void _RefreshAvailableUsers()
        {
            // refresh available users
            List<Client> available = new List<Client>();
            foreach (Client c in _onlines.Keys.ToList<Client>())
            {
                if (c.IsAvailable)
                    available.Add(c);
            }

            // loop on online list client
            foreach (IPortalCallback callback in _onlines.Values)
            {
                try
                {
                    // Send notify
                    callback.RefreshAvailableUser(available);
                }
                catch { }
            }
        }

        public void RefreshInitAvailableUser()
        {
            this._RefreshAvailableUsers();
        }

        public void ConfirmShipsPlacement(Client sender, Grid placement)
        {
            try
            {
                Battle temp = _GetBattleByUsername(sender.Username);
                if (temp.Username1 == sender.Username)
                {
                    _GetBattleByUsername(sender.Username).Cpt1_grid = placement;
                    _GetUserCallback(temp.Username2).NotifyPlacementDone(sender);
                }
                else
                {
                    _GetBattleByUsername(sender.Username).Cpt2_grid = placement;
                    _GetUserCallback(temp.Username1).NotifyPlacementDone(sender);
                }
            }
            catch
            {
            }
        }

        public void GetStatistic()
        {
            try
            {
                List<BattleReport> bReport = dataConnection.GetAllBattleReports();
                List<CptStats> cReport = dataConnection.GetAllCaptainStats();
                List<FractionStats> fReport = dataConnection.GetAllFractionStats();
                List<Captain> captains = dataConnection.GetAllCaptains();
                CurrentCallback.NotifyStatistic(bReport, cReport, fReport, captains);
            }
            catch { }
        }


        /// <summary>
        /// Method for get enemy's data as client
        /// </summary>
        public void GetEnemyClientAndMe()
        {
            try
            {
                Battle temp = _GetBattleByUsername(_currentClient.Username);

                if (temp.Username1 == _currentClient.Username)
                {
                    Client enemy = _GetUserByName(temp.Username2);
                    CurrentCallback.NotifyEnemyClientAndMe(enemy, _currentClient, temp.Cpt1_grid.Ships, temp.Cpt2_grid.Ships);
                }
                else // current client = user 2
                {
                    Client enemy = _GetUserByName(temp.Username1);
                    CurrentCallback.NotifyEnemyClientAndMe(enemy, _currentClient, temp.Cpt2_grid.Ships, temp.Cpt1_grid.Ships);
                }
            }
            catch { }
        }


        public void ReadyForBattle()
        {
            try
            {
                Battle temp = _GetBattleByUsername(_currentClient.Username);
                if (temp.Username1 == _currentClient.Username)
                {
                    _GetBattleByUsername(_currentClient.Username).IsReady1 = true;
                    _GetUserCallback(temp.Username2).NotifyPlayerReady(_currentClient);
                    
                    if (temp.IsReady2)
                    {
                        Message m = new Message();
                        m.Sender = _currentClient;
                        m.Respond = false;
                        _GetUserCallback(_currentClient.Username).ReceiveGameStarted(m);

                        m.Respond = true;
                        _GetUserCallback(temp.Username2).ReceiveGameStarted(m);
                    }
                }
                else
                {
                    _GetBattleByUsername(_currentClient.Username).IsReady2 = true;
                    _GetUserCallback(temp.Username1).NotifyPlayerReady(_currentClient);
                    
                    if (temp.IsReady1)
                    {
                        Message m = new Message();
                        m.Sender = _currentClient;
                        m.Respond = false;
                        _GetUserCallback(_currentClient.Username).ReceiveGameStarted(m);

                        m.Respond = true;
                        _GetUserCallback(temp.Username1).ReceiveGameStarted(m);
                    }
                }
               
            }
            catch
            {

            }
        }

        public void PauseBattle()
        {
            try
            {
                Battle temp = _GetBattleByUsername(_currentClient.Username);
                Turn t = new Turn();
                t.Pass = false;
                if (temp.Username1 == _currentClient.Username)
                {
                    _GetUserCallback(temp.Username2).NotifyPauseBattle(_currentClient, t);
                }
                else
                {
                    _GetUserCallback(temp.Username1).NotifyPauseBattle(_currentClient, t);
                }
            }
            catch { }
        }

        public void ResumeBattle()
        {
            try
            {
                Battle temp = _GetBattleByUsername(_currentClient.Username);
                Turn t = new Turn();
                t.Pass = true;
                if (temp.Username1 == _currentClient.Username)
                {
                    _GetUserCallback(temp.Username2).NotifyResumeBattle(_currentClient, t);
                }
                else
                {
                    _GetUserCallback(temp.Username1).NotifyResumeBattle(_currentClient, t);
                }
            }
            catch { }
        }

        public void Shot(Turn turn)
        {
            try
            {
                if (turn.Shot)
                {
                    Battle temp = _GetBattleByUsername(_currentClient.Username);
                    if (temp.Username1 == _currentClient.Username)
                    {
                        int targeted;
                        bool hit = temp.Cpt2_grid.Ships.ContainsKey(turn.Target_tile);
                        if (hit)
                            targeted = temp.Cpt2_grid.Ships[turn.Target_tile];
                        else
                            targeted = 0;
                        Message m = new Message();
                        m.Sender = _currentClient;
                        switch (targeted)
                        {
                            case 0:
                                _GetUserCallback(temp.Username2).NotifyGotShot(_currentClient, turn);
                                m.Respond = false;
                                m.Content = "You missed.";
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case PATROL:
                                _GetUserCallback(temp.Username2).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Patrol Ship.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer2--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case SUBMARINE:
                                _GetUserCallback(temp.Username2).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Submarine.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer2--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case DESTROYER:
                                _GetUserCallback(temp.Username2).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Destroyer.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer2--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case BATTLESHIP:
                                _GetUserCallback(temp.Username2).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Battleship.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer2--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case CARRIER:
                                _GetUserCallback(temp.Username2).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Carrier Ship.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer2--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                        }
                        if (_GetBattleByUsername(_currentClient.Username).HpPlayer2 == 0)
                        {
                            Message messageOver = new Message();
                            messageOver.Sender = _currentClient;
                            messageOver.Respond = false;
                            messageOver.Content = "You lose";
                            _GetUserCallback(temp.Username2).NotifyBattleOver(messageOver);

                            messageOver.Respond = true;
                            messageOver.Content = "You win";
                            CurrentCallback.NotifyBattleOver(messageOver);

                            this._currentClient.IsAvailable = true;
                            this._GetUserByName(temp.Username2).IsAvailable = true;
                            _RefreshAvailableUsers();

                            BattleReport bReport = new BattleReport(_currentClient.CaptainId, _GetUserByName(temp.Username2).CaptainId, _currentClient.Fraction, _GetUserByName(temp.Username2).Fraction, _currentClient.CaptainId, DateTime.Now, DateTime.Now, 100, 50, 50, 25);

                            dataConnection.AddBattleReport(bReport);
                        }
                    }
                    else
                    {
                        int targeted;
                        bool hit = temp.Cpt1_grid.Ships.ContainsKey(turn.Target_tile);
                        if (hit)
                            targeted = temp.Cpt1_grid.Ships[turn.Target_tile];
                        else
                            targeted = 0;
                        Message m = new Message();
                        m.Sender = _currentClient;
                        switch (targeted)
                        {
                            case 0:
                                _GetUserCallback(temp.Username1).NotifyGotShot(_currentClient, turn);
                                m.Respond = false;
                                m.Content = "You missed.";
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case PATROL:
                                _GetUserCallback(temp.Username1).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Patrol Ship.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer1--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case SUBMARINE:
                                _GetUserCallback(temp.Username1).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Submarine.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer1--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case DESTROYER:
                                _GetUserCallback(temp.Username1).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Destroyer.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer1--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case BATTLESHIP:
                                _GetUserCallback(temp.Username1).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Battleship.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer1--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                            case CARRIER:
                                _GetUserCallback(temp.Username1).NotifyGotShot(_currentClient, turn);
                                m.Respond = true;
                                m.Content = "You hit A Carrier Ship.";
                                _GetBattleByUsername(_currentClient.Username).HpPlayer1--;
                                _GetUserCallback(_currentClient.Username).ReceiveShotResult(m);
                                break;
                        }
                        if (_GetBattleByUsername(_currentClient.Username).HpPlayer1 == 0)
                        {
                            Message messageOver = new Message();
                            messageOver.Sender = _currentClient;
                            messageOver.Respond = false;
                            messageOver.Content = "You lose";
                            _GetUserCallback(temp.Username1).NotifyBattleOver(messageOver);

                            messageOver.Respond = true;
                            messageOver.Content = "You win";
                            CurrentCallback.NotifyBattleOver(messageOver);

                            this._currentClient.IsAvailable = true;
                            this._GetUserByName(temp.Username1).IsAvailable = true;

                            lock (_currentBattle)
                            {
                                _currentBattle.Remove(temp);
                            }
                            _RefreshAvailableUsers();

                            BattleReport bReport = new BattleReport(_currentClient.CaptainId, _GetUserByName(temp.Username1).CaptainId, _currentClient.Fraction, _GetUserByName(temp.Username1).Fraction, _currentClient.CaptainId, DateTime.Now, DateTime.Now, 100, 50, 50, 25);
                            dataConnection.AddBattleReport(bReport);
                        }
                    }
                }
            }
            catch { }
        }
    }
}
